﻿// Include necessary headers
#include "include/GameEntities.h"
#include <conio.h>
#include <cstring>
#include "include/graphics.h"
#include "include/resource.h"
#include <string>
#include <Windows.h>
// RD rework of BIT CSD1 Zhanbo Shi, 2024-3-30, All codes Done by Zhanbo Shi

// Global variable to track the current number of steps
int currentSteps = 0;
// Last state of the game for undo functionality
vector<vector<Tile>> lastState;
// Initial states of levels for resetting
vector<vector<Tile>> initialLevels[5];

// Maps for five different levels
vector<vector<Tile>> levels[5] = {
	{
		{ WALL, WALL, WALL, WALL, WALL, WALL, WALL, WALL },
		{ WALL, GOAL, FLOOR, FLOOR, FLOOR, FLOOR, GOAL, WALL },
		{ WALL, FLOOR, WALL, FLOOR, FLOOR, FLOOR, FLOOR, WALL },
		{ WALL, FLOOR, FLOOR, FLOOR, WALL, WALL, FLOOR, WALL },
		{ WALL, FLOOR, WALL, FLOOR, FLOOR, BOX, FLOOR, WALL },
		{ WALL, FLOOR, FLOOR, FLOOR, FLOOR, FLOOR, BOX, WALL },
		{ WALL, FLOOR, FLOOR, WALL, FLOOR, FLOOR, PLAYER, WALL },
		{ WALL, WALL, WALL, WALL, WALL, WALL, WALL, WALL }
	},

	{
		{WALL, WALL, WALL, WALL, WALL, WALL, WALL, WALL},
		{WALL, PLAYER, FLOOR, FLOOR, FLOOR, FLOOR, GOAL, WALL},
		{WALL, FLOOR, BOX, FLOOR, FLOOR, FLOOR, FLOOR, WALL},
		{WALL, FLOOR, BOX, FLOOR, FLOOR, FLOOR, FLOOR, WALL},
		{WALL, FLOOR, FLOOR, FLOOR, FLOOR, FLOOR, FLOOR, WALL},
		{WALL, FLOOR, FLOOR, FLOOR, FLOOR, FLOOR, FLOOR, WALL},
		{WALL, WALL, FLOOR, FLOOR, FLOOR, FLOOR, GOAL, WALL},
		{WALL, WALL, WALL, WALL, WALL, WALL, WALL, WALL}
	},
	{
		{WALL, WALL, WALL, WALL, WALL, WALL, WALL, WALL},
		{WALL, PLAYER, FLOOR, BOX, FLOOR, GOAL, FLOOR, WALL},
		{WALL, FLOOR, WALL, FLOOR, WALL, FLOOR, FLOOR, WALL},
		{WALL, FLOOR, FLOOR, FLOOR, FLOOR, WALL, FLOOR, WALL},
		{WALL, WALL, WALL, FLOOR, WALL, WALL, FLOOR, WALL},
		{WALL, FLOOR, BOX, FLOOR, FLOOR, GOAL, FLOOR, WALL},
		{WALL, FLOOR, FLOOR, FLOOR, FLOOR, FLOOR, FLOOR, WALL},
		{WALL, WALL, WALL, WALL, WALL, WALL, WALL, WALL}
	},
	 {
		{WALL, WALL, WALL, WALL, WALL, WALL, WALL, WALL},
		{WALL, PLAYER, FLOOR, BOX, FLOOR, GOAL, FLOOR, WALL},
		{WALL, FLOOR, FLOOR, FLOOR, WALL, FLOOR, FLOOR, WALL},
		{WALL, FLOOR, BOX, FLOOR, FLOOR, WALL, GOAL, WALL},
		{WALL, WALL, WALL, FLOOR, FLOOR, WALL, FLOOR, WALL},
		{WALL, FLOOR, BOX, FLOOR, FLOOR, GOAL, FLOOR, WALL},
		{WALL, FLOOR, FLOOR, FLOOR, FLOOR, FLOOR, FLOOR, WALL},
		{WALL, WALL, WALL, WALL, WALL, WALL, WALL, WALL}
	},
	{
		{WALL, WALL, WALL, WALL, WALL, WALL, WALL, WALL},
		{WALL, GOAL, FLOOR, FLOOR, FLOOR, BOX, PLAYER, WALL},
		{WALL, FLOOR, WALL, BOX, FLOOR, FLOOR, GOAL, WALL},
		{WALL, FLOOR, FLOOR, GOAL, WALL, WALL, FLOOR, WALL},
		{WALL, FLOOR, WALL, FLOOR, FLOOR, FLOOR, FLOOR, WALL},
		{WALL, FLOOR, FLOOR, BOX, FLOOR, WALL, FLOOR, WALL},
		{WALL, FLOOR, FLOOR, WALL, FLOOR, FLOOR, FLOOR, WALL},
		{WALL, WALL, WALL, WALL, WALL, WALL, WALL, WALL}
	}
};

vector<vector<Tile>> Startlevels[1] = {
{
	{ WALL, WALL, WALL, WALL, WALL, WALL, WALL, WALL },
	{ WALL, GOAL, FLOOR, FLOOR, FLOOR, FLOOR, GOAL, WALL },
	{ WALL, FLOOR, WALL, BOX, FLOOR, FLOOR, FLOOR, WALL },
	{ WALL, FLOOR, FLOOR, GOAL, WALL, WALL, FLOOR, WALL },
	{ WALL, FLOOR, WALL, FLOOR, FLOOR, BOX, FLOOR, WALL },
	{ WALL, FLOOR, FLOOR, FLOOR, FLOOR, FLOOR, BOX, WALL },
	{ WALL, FLOOR, FLOOR, WALL, FLOOR, FLOOR, PLAYER, WALL },
	{ WALL, WALL, WALL, WALL, WALL, WALL, WALL, WALL }
}
};

class Level {
public:
	Level() {}; // Constructor for initializing a level
	void drawMap(const vector<vector<Tile>>& map); // Method to draw the map
	void clearArea(int left, int top, int right, int bottom) const; // Method to clear a specific area
	void drawUI() const; // Method to draw the UI
	void drawButtons() const; // Method to draw buttons
	int initializeGoalCounts(const vector<vector<Tile>>& level); // Method to initialize the count of goals

	bool updateMap(vector<vector<Tile>>& map, Direction dir); // Function to update the map based on player movement and direction

private:
	vector<vector<Tile>> mapLevel; // Current level map
	Player playerMove;
};

// Method to draw the map
void Level::drawMap(const vector<vector<Tile>>& map) {
	ResourceManager res;
	for (int y = 0; y < HEIGHT; ++y) {
		for (int x = 0; x < WIDTH; ++x) {
			int posX = x * 64; // Width of each tile in pixels
			int posY = y * 64; // Height of each tile in pixels
			switch (map[y][x]) {
			case WALL:
				res.loadResourceImage(posX, posY, IDB_WALL); break;
			case FLOOR:
				res.loadResourceImage(posX, posY, IDB_FLOOR); break;
			case BOX:
				res.loadResourceImage(posX, posY, IDB_BOX); break;
			case GOAL:
				res.loadResourceImage(posX, posY, IDB_GOAL); break;
			case PLAYER:
				res.loadResourceImage(posX, posY, IDB_Player); break;
			case PLAYER_ON_GOAL:
				res.loadResourceImage(posX, posY, IDB_Player); break;
			case BOX_ON_GOAL:
				res.loadResourceImage(posX, posY, IDB_BOX); break;
			}
		}
	}
}

// Function to initialize the count of goals in the current level
int Level::initializeGoalCounts(const vector<vector<Tile>>& level) {
	int goalCount = 0;
	for (const auto& row : level) {
		for (Tile tile : row) {
			if (tile == GOAL || tile == BOX_ON_GOAL) {
				goalCount++;
			}
		}
	}
	return goalCount;
}
// Function to clear a specified area on the screen
void Level::clearArea(int left, int top, int right, int bottom) const {
	setfillcolor(BLACK);
	solidrectangle(left, top, right, bottom); // Clear the specified area
}
// Function to draw the UI elements
void Level::drawUI() const {
	clearArea(520, 400, 700, 600); // Clear the area for the step counter
	wchar_t stepsStr[100];
	swprintf(stepsStr, 100, L"Steps: %d", currentSteps);// Display the current step count
	outtextxy(520, 400, stepsStr);

	outtextxy(520, 450, L"Undo Last Step"); // Button to undo the last step
	outtextxy(520, 500, L"Return Homepage"); // Button to return to the homepage
	outtextxy(520, 550, L"Reset Current Level"); // Button to reset the current level
}

// Function to draw buttons on the Homepage UI
void Level::drawButtons() const {
	clearArea(520, 050, 700, 600);
	settextstyle(20, 0, _T("Arial"));
	outtextxy(520, 100, _T("Start Game"));
	outtextxy(520, 200, _T("Select Level"));
	outtextxy(520, 300, _T("Quit Game"));
}
// Function to update the map based on player movement and direction
bool Level::updateMap(vector<vector<Tile>>& map, Direction dir) {
	lastState = map; // Store the current state for undo functionality
	Player playerMove;
	bool moved = playerMove.move(map, dir);// Move the player
	if (moved) {
		currentSteps++;
	}
	return moved;
}
// Function to copy the initial levels for resetting purposes
void copyLevels() {
	for (int i = 0; i < 5; ++i) {
		initialLevels[i] = levels[i];
	}
}

// Game class declaration
class Game {
public:
	Game() : gameRunning(false), needRefresh(false), currentLevel(0), clear_button(true) {}; // Constructor to initialize the game
	void run(); // Method to start the game
	bool isLevelCompleted(const vector<vector<Tile>>& map); // Method to check if the current level is completed
	int selectLevel();  // Method to select a level
	int showHomePage(); // Method to show the homepage

private:
	bool gameRunning;   // Flag to indicate if the game is running
	bool needRefresh;   // Flag to indicate if the screen needs to be refreshed

	int currentLevel;   // Index of the current level
	int goalCount;      // Count of goals in the current level
	bool clear_button;  // Flag to indicate if the screen needs to be cleare
};

// Method to show the homepage and allow the player to start the game or select a level
int Game::showHomePage() {
	bool homepage = false;

	Level homeLevel = Level();

	while (!homepage) {
		cleardevice(); // Clear the screen
		homeLevel.drawMap(Startlevels[0]); /// Draw the start level map

		setbkmode(TRANSPARENT);
		settextstyle(48, 0, _T("Consolas"));
		settextcolor(WHITE);
		outtextxy(180, 64, _T("Sokoban"));  // Set the font and size for the "Sokoban" text

		homeLevel.drawButtons(); // Draw the buttons on the homepage

		if (MouseHit()) {
			MOUSEMSG m = GetMouseMsg();
			if (m.uMsg == WM_LBUTTONDOWN) {
				if (m.x >= 520 && m.x <= 680 && m.y >= 200 && m.y <= 230) {
					// Level selection button
					int level = selectLevel();
					if (level != -1) {
						homepage = true;
						return ++level; // Return the selected level
					}
				}
				switch (m.uMsg) {
				case WM_LBUTTONDOWN:
					if (m.x >= 520 && m.x <= 680 && m.y >= 100 && m.y <= 130) {
						// Start game button
						homepage = true; // Start the game and quit 'while' loop
						return 0;

					}
					else if (m.x >= 520 && m.x <= 680 && m.y >= 300 && m.y <= 330) {
						// Quit game button
						return -1; // Exit the program
					}

					break;
				}
			}
		}

		FlushBatchDraw();
		Sleep(20);
	}
}
// Method to check if the current level is completed by counting the boxes on goal tiles
bool Game::isLevelCompleted(const vector<vector<Tile>>& map) {
	int boxesOnGoal = 0;
	for (int y = 0; y < HEIGHT; ++y) {
		for (int x = 0; x < WIDTH; ++x) {
			if (map[y][x] == BOX_ON_GOAL) {
				boxesOnGoal++;
			}
		}
	}
	return boxesOnGoal == goalCount;
}
// RD rework of BIT CSD1 Zhanbo Shi, 2024-3-30, All codes Done by Zhanbo Shi

// Method to allow the player to select a level
int Game::selectLevel() {
	int selectedLevel = -1;
	while (selectedLevel == -1) {
		cleardevice();
		settextstyle(24, 0, _T("Consolas"));

		// Draw the level selection options
		for (int i = 0; i < 5; ++i) {
			wchar_t levelStr[100];
			swprintf(levelStr, 100, L"Level No.%d", i + 1);
			outtextxy(520, 100 + i * 50, levelStr);
		}
		if (MouseHit()) {
			MOUSEMSG m = GetMouseMsg();
			if (m.uMsg == WM_LBUTTONDOWN) {
				for (int i = 0; i < 5; ++i) {   // 'for' loop to check which Level has been clicked.
					if (m.x >= 520 && m.x <= 680 && m.y >= (100 + i * 50) && m.y <= (130 + i * 50)) {
						selectedLevel = i;
						break;
					}
				}
			}
		}
		FlushBatchDraw();
		Sleep(20);
	}
	return selectedLevel;
}

// Method to run the game, including the main game loop
void Game::run() {

	Level gameLevel = Level();

	currentLevel = showHomePage();
	if (currentLevel > 0 && currentLevel <= 5) {
		currentLevel--;
	}

	goalCount = gameLevel.initializeGoalCounts(levels[currentLevel]);

	if (currentLevel != -1) {
		levels[currentLevel] = initialLevels[currentLevel]; // Load the selected level
		gameRunning = true;
	}
	currentSteps = 0;
	bool isKeyPressed = false;

	while (gameRunning) {
		gameLevel.drawMap(levels[currentLevel]); // Draw the map of the current level
		gameLevel.drawUI(); // Draw the in 'Level'UI buttons
		if (clear_button) {
			cleardevice(); // Clear the screen
			clear_button = false;
		}
		bool actionTaken = false;

		// Check keyboard input(Direction pad only)
		Direction dir;
		bool key_pressed = false;
		if (!isKeyPressed) {
			// 检查键盘输入
			if (GetAsyncKeyState(VK_UP) & 0x8000) {  // Up arrow key
				dir = UP;
				key_pressed = true;
			}
			else if (GetAsyncKeyState(VK_DOWN) & 0x8000) { // Down arrow key
				dir = DOWN;
				key_pressed = true;
			}
			else if (GetAsyncKeyState(VK_LEFT) & 0x8000) {  // Left arrow key
				dir = LEFT;
				key_pressed = true;
			}
			else if (GetAsyncKeyState(VK_RIGHT) & 0x8000) { // Right arrow key
				dir = RIGHT;
				key_pressed = true;
			}

			if (key_pressed) {
				actionTaken = true;
				needRefresh = gameLevel.updateMap(levels[currentLevel], dir);
				// If a move occurred, set the flag to prevent repeated movement(long press)
				if (needRefresh) {
					isKeyPressed = true;  // Prevent further movement until the key is released
					if (isLevelCompleted(levels[currentLevel])) {
						// If the current level is completed, move to the next level
						currentLevel++;
						currentSteps = 0; // Reset the step counter
						goalCount = gameLevel.initializeGoalCounts(levels[currentLevel]); // reset goalcount to fit next level
						if (currentLevel < 5) {
							levels[currentLevel] = initialLevels[currentLevel];
						}
						else {
							// If all levels are completed
							MessageBox(GetHWnd(), _T("Congratulations! You have completed all levels!"), _T("game completed"), MB_OK);
							gameRunning = false;
						}
					}
					FlushBatchDraw();
					needRefresh = false;
				}
			}
		}
		else if (!(GetAsyncKeyState(VK_UP) & 0x8000) && !(GetAsyncKeyState(VK_DOWN) & 0x8000) &&
			!(GetAsyncKeyState(VK_LEFT) & 0x8000) && !(GetAsyncKeyState(VK_RIGHT) & 0x8000)) {
			// If no direction keys are pressed, reset the key press state
			isKeyPressed = false;
		}

		// Check for mouse clicks
		if (MouseHit()) {
			MOUSEMSG m = GetMouseMsg();
			switch (m.uMsg) {
			case WM_LBUTTONDOWN:
				// Return Homepage button
				if (m.x >= 520 && m.x <= 680 && m.y >= 500 && m.y <= 530) {
					currentLevel = showHomePage();
					if (currentLevel == -1) {
						gameRunning = false; // 退出游戏
					}
					else if (currentLevel > 0 && currentLevel <= 5) {
						currentLevel--;
					}
					// Reset the game state
					currentSteps = 0;
					levels[currentLevel] = initialLevels[currentLevel];
					goalCount = gameLevel.initializeGoalCounts(levels[currentLevel]);
					clear_button = true;
				}
				// Reset Level button
				else if (m.x >= 520 && m.x <= 680 && m.y >= 550 && m.y <= 580) {
					levels[currentLevel] = initialLevels[currentLevel];
					currentSteps = 0; // Reset the step counter
					goalCount = gameLevel.initializeGoalCounts(levels[currentLevel]);
					needRefresh = true;
				}
				// Undo last step button
				else if (m.x >= 520 && m.x <= 680 && m.y >= 450 && m.y <= 480) {
					if (!lastState.empty()) {
						levels[currentLevel] = lastState;
						currentSteps = max(0, currentSteps - 1); // Decrement step count, ensuring it doesn't go below 0
						needRefresh = true;
					}
				}
				else if (m.x >= 520 && m.x <= 680 && m.y >= 300 && m.y <= 330) {
					gameRunning = false; // Exit the game by quit 'while' loop
				}
				break;
			}
			actionTaken = true;
		}

		FlushBatchDraw();
		Sleep(16);
	}
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
	initgraph(700, 600); // Initialize a 700x600 pixel graphics window
	BeginBatchDraw();
	cleardevice();

	copyLevels();   // Copy the initial levels for resetting
	Game newGame = Game();  // Create a new game instance
	newGame.run();  // Start the game

	EndBatchDraw();
	closegraph(); // Close the graphics window
	return 0;
}